package com.ahdms.es.util;

import com.ahdms.es.bean.SesSignInfo;
import com.ahdms.es.engine.SesSignatureVerifyEngine;
import com.ahdms.es.exceptions.ApiException;
import com.ahdms.es.exceptions.DocNotSignException;
import com.ahdms.es.exceptions.OFDVerifyException;
import com.ahdms.es.gm.v4.SES_Signature;
import com.ahdms.es.gm.v4.SESeal;
import com.ahdms.es.parse.SESVersion;
import com.ahdms.es.parse.SESVersionHolder;
import com.ahdms.es.parse.VersionParser;
import com.ahdms.es.result.VerifyResult;
import org.dom4j.DocumentException;
import org.ofdrw.core.basicType.ST_Loc;
import org.ofdrw.core.signatures.SigType;
import org.ofdrw.core.signatures.Signature;
import org.ofdrw.core.signatures.Signatures;
import org.ofdrw.core.signatures.appearance.Seal;
import org.ofdrw.core.signatures.range.Reference;
import org.ofdrw.core.signatures.sig.SignedInfo;
import org.ofdrw.reader.BadOFDException;
import org.ofdrw.reader.OFDReader;
import org.ofdrw.reader.ResourceLocator;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

/**
 * @author qinxiang
 * @date 2021-04-15 9:42
 */
public class OfdVerifyUtils {

    public static List<VerifyResult<SesSignInfo>> verify(OFDReader reader) throws OFDVerifyException {
        List<VerifyResult<SesSignInfo>> results = new ArrayList<>();

        ResourceLocator rl = reader.getResourceLocator();
        rl.save();

        try {
            rl.cd("/");
            // 获取电子签名列表文件路径
            ST_Loc signsListLoc = reader.getDefaultDocSignaturesPath();
            if (signsListLoc == null) {
                throw new DocNotSignException("文件未进行电子签名");
            }
            // 获取签名列表
            Signatures sigList = rl.get(signsListLoc, Signatures::new);
            // 切换目录到 签名列表文件所处目录 “/Doc_0/Signs”
            rl.cd(signsListLoc.parent());
            // 获取签名记录列表
            List<Signature> signatures = sigList.getSignatures();
            for (org.ofdrw.core.signatures.Signature sigRecord : signatures) {
                SigType type = sigRecord.getType();
                // 获取 Signature.xml 文件路径
                ST_Loc signFileLoc = sigRecord.getBaseLoc();
                Path signatureFilePath = rl.getFile(signFileLoc);
                org.ofdrw.core.signatures.sig.Signature sig = rl.get(signFileLoc, org.ofdrw.core.signatures.sig.Signature::new);
                // 1. 检查文件完整性
                checkFileIntegrity(rl, sig);

                rl.save();
                try {
                    // 获取 SignedValue.dat 文件路径
                    rl.cd(signFileLoc.parent());
                    Path signedValueFilePath = rl.getFile(sig.getSignedValue());
                    if (type == null || type == SigType.Seal) {
                        Seal seal = sig.getSignedInfo().getSeal();
                        /*
                         * 由于 Seal节点在OFD中是可选节点，即便是电子签章也为可选，
                         * 所以这里只有在该元素存在的情况在进行匹配检查。
                         */
                        if (seal != null) {
                            // 获取电子印章 Seal.esl 文件路径
                            Path sealFilePath = rl.getFile(seal.getBaseLoc());
                            // 2. 检查印章匹配
                            VerifyResult verifyResult = checkSealMatch(sealFilePath, signedValueFilePath);
                            if (!verifyResult.isResult()) {
                                results.add(verifyResult);
                                break;
                            }
                        }
                    }
                    // 签名算法名称
                    String alg = sig.getSignedInfo().getSignatureMethod();
                    // 3. 验证电子签名或签章数据
                    results.add(checkSignedValue(type, alg, signatureFilePath, signedValueFilePath));
                } finally {
                    rl.restore();
                }
            }

            return results;
        } catch (FileNotFoundException | DocumentException e) {
            throw new BadOFDException("OFD文件内部结构错误，无法解析。", e);
        } finally {
            rl.restore();
        }

    }

    /**
     * 验证电子签章数据
     *
     * @param type
     * @param alg
     * @param signatureFilePath 原文的路径
     * @param signedValuePath   电子签章文件的路径
     * @throws IOException
     */
    private static VerifyResult<SesSignInfo> checkSignedValue(SigType type, String alg, Path signatureFilePath, Path signedValuePath) {
        try {
            return SesSignatureVerifyEngine.verifySesSignature(Files.readAllBytes(signedValuePath), Files.readAllBytes(signatureFilePath));
        } catch (IOException e) {
            e.printStackTrace();
            return VerifyResult.error("电子签章文件读取失败.");
        }
    }

    /**
     * 比对印章数据
     *
     * @param sealPath        印章文件在OFD文档中的路径
     * @param signedValuePath 电子签章文件的路径
     * @return VerifyResult
     * @throws IOException
     * @throws OFDVerifyException
     */
    private static VerifyResult checkSealMatch(Path sealPath, Path signedValuePath) {
        try {
            byte[] sesSignatureBin = Files.readAllBytes(signedValuePath);
            byte[] sealBin = Files.readAllBytes(sealPath);
            if (checkSealMatch(sealBin, sesSignatureBin)) {
                return VerifyResult.success();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (OFDVerifyException e) {
            e.printStackTrace();
            return VerifyResult.error(e.getMessage());
        }
        return VerifyResult.error("印章数据比对有误");
    }

    /**
     * 比对印章数据
     *
     * @param sealBin         印章文件数据
     * @param sesSignatureBin 电子签章文件数据
     * @return
     * @throws IOException
     * @throws OFDVerifyException
     */
    private static boolean checkSealMatch(byte[] sealBin, byte[] sesSignatureBin) throws OFDVerifyException, IOException {

        byte[] expect = null;
        SESVersionHolder v = VersionParser.parseSES_SignatureVersion(sesSignatureBin);
        if (v.getVersion() == SESVersion.v4) {
            SES_Signature sesSignature = SES_Signature.getInstance(v.getObjSeq());
            SESeal eseal = sesSignature.getToSign().getEseal();
            expect = eseal.getEncoded();
        } else {
            if (v.getVersion() != SESVersion.v1) {
                throw new OFDVerifyException("未知的电子签章数据版本，无法解析");
            }
            com.ahdms.es.gm.v1.SES_Signature sesSignature = com.ahdms.es.gm.v1.SES_Signature.getInstance(v.getObjSeq());
            com.ahdms.es.gm.v1.SESeal eseal = sesSignature.getToSign().getEseal();
            expect = eseal.getEncoded("DER");
        }
        return Arrays.equals(expect, sealBin);
    }

    /**
     * 检查被保护文件的完整性（是否被篡改）
     *
     * @param rl
     * @param sig
     */
    private static void checkFileIntegrity(ResourceLocator rl, org.ofdrw.core.signatures.sig.Signature sig) {
        rl.save();
        SignedInfo signedInfo = sig.getSignedInfo();
        List<Reference> references = signedInfo.getReferences().getReferences();
        try {
            if (references != null && references.size() != 0) {
                for (Reference reference : references) {

                    ST_Loc fileRef = reference.getFileRef();
                    Path file = rl.getFile(fileRef);
                    byte[] expectDataHash = reference.getCheckValue();
                    byte[] actualData = Files.readAllBytes(file);
                    byte[] actualDataHash = CalculateHashUtils.sm3Digest(actualData);
                    if (!Arrays.equals(expectDataHash, actualDataHash)) {
                        throw new ApiException("文件被篡改");
                    }
                }
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            rl.restore();
        }
    }

}
